/**
 * Copyright 2020 jianggujin (www.jianggujin.com).
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *  
 *      http://www.apache.org/licenses/LICENSE-2.0
 *  
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.jianggujin.dbfly.mybatis.entity;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.apache.ibatis.reflection.MetaObject;

import com.jianggujin.dbfly.mybatis.util.JMetaObjectUtils;
import com.jianggujin.dbfly.util.JDBFlyException;

/**
 * @author jianggujin
 *
 */
public abstract class JGeneratedCriteria {
    protected List<JCriterion> criteria;
    // 字段是否必须存在
    protected boolean exists;
    // 值是否不能为空
    protected boolean notNull;
    // 连接条件
    protected String andOr;
    protected JEntity entity;
    protected boolean isOr = false;

    protected JGeneratedCriteria(JEntity entity, boolean exists, boolean notNull) {
        super();
        this.exists = exists;
        this.notNull = notNull;
        this.criteria = new ArrayList<JCriterion>();
        this.entity = entity;
    }

    /**
     * 属性转列明
     * 
     * @param property
     * @return
     */
    private String column(String property) {
        if (this.entity.hasProperty(property)) {
            return this.entity.getColumn(property).getColumn();
        } else if (exists) {
            throw new JDBFlyException("当前实体类不包含名为{}的属性!", property);
        } else {
            return null;
        }
    }

    private String property(String property) {
        if (this.entity.hasProperty(property)) {
            return property;
        } else if (exists) {
            throw new JDBFlyException("当前实体类不包含名为{}的属性!", property);
        } else {
            return null;
        }
    }

    protected void addCriterion(String condition, boolean isOr) {
        if (condition == null) {
            throw new JDBFlyException("Value for condition cannot be null");
        }
        if (condition.startsWith("null")) {
            return;
        }
        criteria.add(new JCriterion(condition, isOr));
    }

    protected void addCriterion(String condition, Object value, String property, boolean isOr) {
        if (value == null) {
            if (notNull) {
                throw new JDBFlyException("Value for {} cannot be null", property);
            } else {
                return;
            }
        }
        if (property == null) {
            return;
        }
        criteria.add(new JCriterion(condition, value, isOr));
    }

    protected void addCriterion(String condition, Object value1, Object value2, String property, boolean isOr) {
        if (value1 == null || value2 == null) {
            if (notNull) {
                throw new JDBFlyException("Between values for {} cannot be null", property);
            } else {
                return;
            }
        }
        if (property == null) {
            return;
        }
        criteria.add(new JCriterion(condition, value1, value2, isOr));
    }

    /*************************** AND相关操作 *************************/
    public JCriteria andIsNull(String property) {
        addCriterion(column(property) + " is null", false);
        return (JCriteria) this;
    }

    public JCriteria andIsNotNull(String property) {
        addCriterion(column(property) + " is not null", false);
        return (JCriteria) this;
    }

    public JCriteria andEqualTo(String property, Object value) {
        addCriterion(column(property) + " =", value, property(property), false);
        return (JCriteria) this;
    }

    public JCriteria andEq(String property, Object value) {
        return this.andEqualTo(property, value);
    }

    public JCriteria andNotEqualTo(String property, Object value) {
        addCriterion(column(property) + " <>", value, property(property), false);
        return (JCriteria) this;
    }

    public JCriteria andNe(String property, Object value) {
        return this.andNotEqualTo(property, value);
    }

    /**
     * 将此对象的不为空的字段参数作为相等查询条件
     * 
     * @param param
     * @return
     */
    public JCriteria andEqualTo(Object param) {
        return this.andEqualTo(param, false);
    }

    /**
     * 将此对象的不为空的字段参数作为相等查询条件
     * 
     * @param param
     * @return
     */
    public JCriteria andEq(Object param) {
        return this.andEqualTo(param);
    }

    /**
     * 将此对象的所有字段参数作为相等查询条件，如果字段为null，则为is null
     *
     * @param param 参数对象
     */
    public JCriteria andAllEqualTo(Object param) {
        return this.andEqualTo(param, true);
    }

    /**
     * 将此对象的所有字段参数作为相等查询条件，如果字段为null，则为is null
     * 
     * @param param
     * @return
     */
    public JCriteria andAllEq(Object param) {
        return this.andAllEqualTo(param);
    }

    /**
     * 将此对象作为查询条件，可以是一个POJO对象，也可以是{@link Map}
     * 
     * @param param
     * @param null2IsNull 为true则在map的value为null时调用isNull方法,为false时则忽略
     * @return
     */
    public JCriteria andEq(Object param, boolean null2IsNull) {
        return this.andEqualTo(param, null2IsNull);
    }

    /**
     * 将此对象作为查询条件，可以是一个POJO对象，也可以是{@link Map}
     * 
     * @param param
     * @param null2IsNull 为true则在map的value为null时调用isNull方法,为false时则忽略
     * @return
     */
    public JCriteria andEqualTo(Object param, boolean null2IsNull) {
        if (param == null) {
            return (JCriteria) this;
        }
        MetaObject metaObject = JMetaObjectUtils.forObject(param);
        String[] properties = metaObject.getGetterNames();
        for (String property : properties) {
            // 属性和列对应Map中有此属性
            if (this.entity.hasProperty(property)) {
                Object value = metaObject.getValue(property);
                // 属性值不为空
                if (value != null) {
                    andEqualTo(property, value);
                } else if (null2IsNull) {
                    andIsNull(property);
                }
            }
        }
        return (JCriteria) this;
    }

    public JCriteria andGreaterThan(String property, Object value) {
        addCriterion(column(property) + " >", value, property(property), false);
        return (JCriteria) this;
    }

    public JCriteria andGt(String property, Object value) {
        return this.andGreaterThan(property, value);
    }

    public JCriteria andGreaterThanOrEqualTo(String property, Object value) {
        addCriterion(column(property) + " >=", value, property(property), false);
        return (JCriteria) this;
    }

    public JCriteria andGe(String property, Object value) {
        return this.andGreaterThanOrEqualTo(property, value);
    }

    public JCriteria andLessThan(String property, Object value) {
        addCriterion(column(property) + " <", value, property(property), false);
        return (JCriteria) this;
    }

    public JCriteria andLt(String property, Object value) {
        return this.andLessThan(property, value);
    }

    public JCriteria andLessThanOrEqualTo(String property, Object value) {
        addCriterion(column(property) + " <=", value, property(property), false);
        return (JCriteria) this;
    }

    public JCriteria andLe(String property, Object value) {
        return this.andLessThanOrEqualTo(property, value);
    }

    public JCriteria andIn(String property, Iterable<?> values) {
        addCriterion(column(property) + " in", values, property(property), false);
        return (JCriteria) this;
    }

    public JCriteria andNotIn(String property, Iterable<?> values) {
        addCriterion(column(property) + " not in", values, property(property), false);
        return (JCriteria) this;
    }

    public JCriteria andBetween(String property, Object value1, Object value2) {
        addCriterion(column(property) + " between", value1, value2, property(property), false);
        return (JCriteria) this;
    }

    public JCriteria andNotBetween(String property, Object value1, Object value2) {
        addCriterion(column(property) + " not between", value1, value2, property(property), false);
        return (JCriteria) this;
    }

    public JCriteria andLike(String property, String value) {
        addCriterion(column(property) + " like", value, property(property), false);
        return (JCriteria) this;
    }

    public JCriteria andLikeLeft(String property, String value) {
        return this.andLike(property, value == null ? null : ("%" + value));
    }

    public JCriteria andLikeRight(String property, String value) {
        return this.andLike(property, value == null ? null : (value + "%"));
    }

    public JCriteria andNotLike(String property, String value) {
        addCriterion(column(property) + " not like", value, property(property), false);
        return (JCriteria) this;
    }

    public JCriteria andNotLikeLeft(String property, String value) {
        return this.andNotLike(property, value == null ? null : ("%" + value));
    }

    public JCriteria andNotLikeRight(String property, String value) {
        return this.andNotLike(property, value == null ? null : (value + "%"));
    }

    /**
     * 手写条件
     *
     * @param condition 例如 "length(countryname)&lt;5"
     * @return
     */
    public JCriteria andCondition(String condition) {
        addCriterion(condition, false);
        return (JCriteria) this;
    }

    /**
     * 手写左边条件，右边用value值
     *
     * @param condition 例如 "length(countryname)="
     * @param value     例如 5
     * @return
     */
    public JCriteria andCondition(String condition, Object value) {
        criteria.add(new JCriterion(condition, value));
        return (JCriteria) this;
    }

    /*************************** OR相关操作 *************************/
    public JCriteria orIsNull(String property) {
        addCriterion(column(property) + " is null", true);
        return (JCriteria) this;
    }

    public JCriteria orIsNotNull(String property) {
        addCriterion(column(property) + " is not null", true);
        return (JCriteria) this;
    }

    public JCriteria orEqualTo(String property, Object value) {
        addCriterion(column(property) + " =", value, property(property), true);
        return (JCriteria) this;
    }

    public JCriteria orEq(String property, Object value) {
        return this.orEqualTo(property, value);
    }

    public JCriteria orNotEqualTo(String property, Object value) {
        addCriterion(column(property) + " <>", value, property(property), true);
        return (JCriteria) this;
    }

    public JCriteria orNe(String property, Object value) {
        return this.orNotEqualTo(property, value);
    }

    /**
     * 将此对象的不为空的字段参数作为相等查询条件
     * 
     * @param param
     * @return
     */
    public JCriteria orEqualTo(Object param) {
        return this.orEqualTo(param, false);
    }

    /**
     * 将此对象的不为空的字段参数作为相等查询条件
     * 
     * @param param
     * @return
     */
    public JCriteria orEq(Object param) {
        return this.orEqualTo(param);
    }

    /**
     * 将此对象的所有字段参数作为相等查询条件，如果字段为null，则为is null
     *
     * @param param 参数对象
     */
    public JCriteria orAllEqualTo(Object param) {
        return this.orEqualTo(param, true);
    }

    /**
     * 将此对象的所有字段参数作为相等查询条件，如果字段为null，则为is null
     *
     * @param param 参数对象
     */
    public JCriteria orAllEq(Object param) {
        return this.orAllEqualTo(param);
    }

    /**
     * 将此对象的所有字段参数作为相等查询条件，如果字段为null，则为is null
     *
     * @param param       参数对象
     * @param null2IsNull 为true则在map的value为null时调用isNull方法,为false时则忽略
     */
    public JCriteria orEq(Object param, boolean null2IsNull) {
        return this.orEqualTo(param, null2IsNull);
    }

    /**
     * 将此对象的所有字段参数作为相等查询条件，如果字段为null，则为is null
     *
     * @param param       参数对象
     * @param null2IsNull 为true则在map的value为null时调用isNull方法,为false时则忽略
     */
    public JCriteria orEqualTo(Object param, boolean null2IsNull) {
        if (param == null) {
            return (JCriteria) this;
        }
        MetaObject metaObject = JMetaObjectUtils.forObject(param);
        String[] properties = metaObject.getGetterNames();
        for (String property : properties) {
            // 属性和列对应Map中有此属性
            if (this.entity.hasProperty(property)) {
                Object value = metaObject.getValue(property);
                // 属性值不为空
                if (value != null) {
                    orEqualTo(property, value);
                } else if (null2IsNull) {
                    orIsNull(property);
                }
            }
        }
        return (JCriteria) this;
    }

    public JCriteria orGreaterThan(String property, Object value) {
        addCriterion(column(property) + " >", value, property(property), true);
        return (JCriteria) this;
    }

    public JCriteria orGt(String property, Object value) {
        return this.orGreaterThan(property, value);
    }

    public JCriteria orGreaterThanOrEqualTo(String property, Object value) {
        addCriterion(column(property) + " >=", value, property(property), true);
        return (JCriteria) this;
    }

    public JCriteria orGe(String property, Object value) {
        return this.orGreaterThanOrEqualTo(property, value);
    }

    public JCriteria orLessThan(String property, Object value) {
        addCriterion(column(property) + " <", value, property(property), true);
        return (JCriteria) this;
    }

    public JCriteria orLt(String property, Object value) {
        return this.orLessThan(property, value);
    }

    public JCriteria orLessThanOrEqualTo(String property, Object value) {
        addCriterion(column(property) + " <=", value, property(property), true);
        return (JCriteria) this;
    }

    public JCriteria orLe(String property, Object value) {
        return this.orLessThanOrEqualTo(property, value);
    }

    public JCriteria orIn(String property, Iterable<?> values) {
        addCriterion(column(property) + " in", values, property(property), true);
        return (JCriteria) this;
    }

    public JCriteria orNotIn(String property, Iterable<?> values) {
        addCriterion(column(property) + " not in", values, property(property), true);
        return (JCriteria) this;
    }

    public JCriteria orBetween(String property, Object value1, Object value2) {
        addCriterion(column(property) + " between", value1, value2, property(property), true);
        return (JCriteria) this;
    }

    public JCriteria orNotBetween(String property, Object value1, Object value2) {
        addCriterion(column(property) + " not between", value1, value2, property(property), true);
        return (JCriteria) this;
    }

    public JCriteria orLike(String property, String value) {
        addCriterion(column(property) + " like", value, property(property), true);
        return (JCriteria) this;
    }

    public JCriteria orLikeLeft(String property, String value) {
        return this.orLike(property, value == null ? null : ("%" + value));
    }

    public JCriteria orLikeRight(String property, String value) {
        return this.orLike(property, value == null ? null : (value + "%"));
    }

    public JCriteria orNotLike(String property, String value) {
        addCriterion(column(property) + " not like", value, property(property), true);
        return (JCriteria) this;
    }

    public JCriteria orNotLikeLeft(String property, String value) {
        return this.orNotLike(property, value == null ? null : ("%" + value));
    }

    public JCriteria orNotLikeRight(String property, String value) {
        return this.orNotLike(property, value == null ? null : (value + "%"));
    }

    /**
     * 手写条件
     *
     * @param condition 例如 "length(countryname)&lt;5"
     * @return
     */
    public JCriteria orCondition(String condition) {
        addCriterion(condition, true);
        return (JCriteria) this;
    }

    /**
     * 手写左边条件，右边用value值
     *
     * @param condition 例如 "length(countryname)="
     * @param value     例如 5
     * @return
     */
    public JCriteria orCondition(String condition, Object value) {
        criteria.add(new JCriterion(condition, value, true));
        return (JCriteria) this;
    }

    /*************************** 链式相关操作 *************************/
    public JCriteria isNull(String property) {
        addCriterion(column(property) + " is null", isOr);
        return (JCriteria) this;
    }

    public JCriteria isNotNull(String property) {
        addCriterion(column(property) + " is not null", isOr);
        return (JCriteria) this;
    }

    public JCriteria equalTo(String property, Object value) {
        addCriterion(column(property) + " =", value, property(property), isOr);
        return (JCriteria) this;
    }

    public JCriteria eq(String property, Object value) {
        return this.equalTo(property, value);
    }

    public JCriteria notEqualTo(String property, Object value) {
        addCriterion(column(property) + " <>", value, property(property), isOr);
        return (JCriteria) this;
    }

    public JCriteria ne(String property, Object value) {
        return this.notEqualTo(property, value);
    }

    /**
     * 将此对象的不为空的字段参数作为相等查询条件
     * 
     * @param param
     * @return
     */
    public JCriteria equalTo(Object param) {
        return this.equalTo(param, false);
    }

    /**
     * 将此对象的不为空的字段参数作为相等查询条件
     * 
     * @param param
     * @return
     */
    public JCriteria eq(Object param) {
        return this.equalTo(param);
    }

    /**
     * 将此对象的所有字段参数作为相等查询条件，如果字段为null，则为is null
     *
     * @param param 参数对象
     */
    public JCriteria allEqualTo(Object param) {
        return this.equalTo(param, true);
    }

    /**
     * 将此对象的所有字段参数作为相等查询条件，如果字段为null，则为is null
     *
     * @param param 参数对象
     */
    public JCriteria allEq(Object param) {
        return this.allEqualTo(param);
    }

    /**
     * 将此对象的所有字段参数作为相等查询条件，如果字段为null，则为is null
     *
     * @param param       参数对象
     * @param null2IsNull 为true则在map的value为null时调用isNull方法,为false时则忽略
     */
    public JCriteria eq(Object param, boolean null2IsNull) {
        return this.equalTo(param, null2IsNull);
    }

    /**
     * 将此对象的所有字段参数作为相等查询条件，如果字段为null，则为is null
     *
     * @param param       参数对象
     * @param null2IsNull 为true则在map的value为null时调用isNull方法,为false时则忽略
     */
    public JCriteria equalTo(Object param, boolean null2IsNull) {
        if (param == null) {
            return (JCriteria) this;
        }
        MetaObject metaObject = JMetaObjectUtils.forObject(param);
        String[] properties = metaObject.getGetterNames();
        for (String property : properties) {
            // 属性和列对应Map中有此属性
            if (this.entity.hasProperty(property)) {
                Object value = metaObject.getValue(property);
                // 属性值不为空
                if (value != null) {
                    equalTo(property, value);
                } else if (null2IsNull) {
                    isNull(property);
                }
            }
        }
        return (JCriteria) this;
    }

    public JCriteria greaterThan(String property, Object value) {
        addCriterion(column(property) + " >", value, property(property), isOr);
        return (JCriteria) this;
    }

    public JCriteria gt(String property, Object value) {
        return this.greaterThan(property, value);
    }

    public JCriteria greaterThanOrEqualTo(String property, Object value) {
        addCriterion(column(property) + " >=", value, property(property), isOr);
        return (JCriteria) this;
    }

    public JCriteria ge(String property, Object value) {
        return this.greaterThanOrEqualTo(property, value);
    }

    public JCriteria lessThan(String property, Object value) {
        addCriterion(column(property) + " <", value, property(property), isOr);
        return (JCriteria) this;
    }

    public JCriteria lt(String property, Object value) {
        return this.lessThan(property, value);
    }

    public JCriteria lessThanOrEqualTo(String property, Object value) {
        addCriterion(column(property) + " <=", value, property(property), isOr);
        return (JCriteria) this;
    }

    public JCriteria le(String property, Object value) {
        return this.lessThanOrEqualTo(property, value);
    }

    public JCriteria in(String property, Iterable<?> values) {
        addCriterion(column(property) + " in", values, property(property), isOr);
        return (JCriteria) this;
    }

    public JCriteria notIn(String property, Iterable<?> values) {
        addCriterion(column(property) + " not in", values, property(property), isOr);
        return (JCriteria) this;
    }

    public JCriteria between(String property, Object value1, Object value2) {
        addCriterion(column(property) + " between", value1, value2, property(property), isOr);
        return (JCriteria) this;
    }

    public JCriteria notBetween(String property, Object value1, Object value2) {
        addCriterion(column(property) + " not between", value1, value2, property(property), isOr);
        return (JCriteria) this;
    }

    public JCriteria like(String property, String value) {
        addCriterion(column(property) + " like", value, property(property), isOr);
        return (JCriteria) this;
    }

    public JCriteria likeLeft(String property, String value) {
        return this.like(property, value == null ? null : ("%" + value));
    }

    public JCriteria likeRight(String property, String value) {
        return this.like(property, value == null ? null : (value + "%"));
    }

    public JCriteria notLike(String property, String value) {
        addCriterion(column(property) + " not like", value, property(property), isOr);
        return (JCriteria) this;
    }

    public JCriteria notLikeLeft(String property, String value) {
        return this.notLike(property, value == null ? null : ("%" + value));
    }

    public JCriteria notLikeRight(String property, String value) {
        return this.notLike(property, value == null ? null : (value + "%"));
    }

    /**
     * 手写条件
     *
     * @param condition 例如 "length(countryname)&lt;5"
     * @return
     */
    public JCriteria condition(String condition) {
        addCriterion(condition, isOr);
        return (JCriteria) this;
    }

    /**
     * 手写左边条件，右边用value值
     *
     * @param condition 例如 "length(countryname)="
     * @param value     例如 5
     * @return
     */
    public JCriteria condition(String condition, Object value) {
        criteria.add(new JCriterion(condition, value, isOr));
        return (JCriteria) this;
    }

    /**
     * 开始AND
     * 
     * @return
     */
    public JCriteria and() {
        this.isOr = false;
        return (JCriteria) this;
    }

    /**
     * 开始OR
     * 
     * @return
     */
    public JCriteria or() {
        this.isOr = true;
        return (JCriteria) this;
    }

    public List<JCriterion> getAllCriteria() {
        return criteria;
    }

    public String getAndOr() {
        return andOr;
    }

    protected void setAndOr(String andOr) {
        this.andOr = andOr;
    }

    public List<JCriterion> getCriteria() {
        return criteria;
    }

    public boolean isValid() {
        return criteria.size() > 0;
    }
}